package net.sansi.v3correctserver.service.correct.areamerge;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import net.sansi.v3correctserver.entity.IntPoint;
import net.sansi.v3correctserver.entity.screen.CorrectArea;
import net.sansi.v3correctserver.entity.screen.Rect;
import net.sansi.v3correctserver.service.correct.RectCorrectBase;
import net.sansi.v3correctserver.util.Matrix;

import java.util.*;

/**
 * Created by Fang
 * create time : 2024/11/26
 * IDE : IntelliJ IDEA
 * <p>
 * 区域融合
 */

@Slf4j
public class RecAreaMerge extends RectCorrectBase {

    private final Params params;

    public RecAreaMerge(Params params) {
        this.params = params;
    }

    /**
     * 融合路口
     * 开始区域融合
     */
    public void toMerge() {
        List<Area> areas = params.areas;
        Set<Area> visited = new HashSet<>();
        Deque<Area> dq = new LinkedList<>();
        dq.add(areas.get(areas.size() / 2));
        while (!dq.isEmpty()) {
            Area area = dq.pollFirst();
            if (visited.contains(area)) continue;
            visited.add(area);
            for (Area a : areas) {
                if (visited.contains(a)) continue;
                for (int dir = 0; dir < 4; dir++) {
                    if (!isNeighborArea(area, a, dir)) continue;
                    mergeArea(area, a, dir);
                    dq.offerLast(a);
                }
            }
        }
    }

    /**
     * 融合两个区域
     *
     * @param area1 区域1
     * @param area2 区域2
     * @param dir   方向 area2 在 area1 的什么方向
     */
    private void mergeArea(Area area1, Area area2, int dir) {
        log.info("正在融合区域 {} 和 {}", area1.getAreaId(), area2.getAreaId());
        for (int i = 0; i < 3; i++) {
            double k = computeCorrectK(area1, area2, dir, i);
            log.info("[k] {}", k);
            Matrix<double[]> result = area2.getResult(i);
            result.foreach((rx, ry, item) -> {
                if (item == null) return;
                item[0] *= k;
                item[1] *= k;
                item[2] *= k;
            });
        }
        smoothResult(area1, area2, dir);
    }

    /**
     * 平滑结果处理
     *
     * @param area1 区域1
     * @param area2 区域2
     * @param dir   方向
     */
    private void smoothResult(Area area1, Area area2, int dir) {
        Rect intersection = getIntersection(area1, area2, dir);
        Area frontArea = dir == 0 || dir == 3 ? area2 : area1;
        Area behindArea = dir == 0 || dir == 3 ? area1 : area2;
        intersection.foreach((x, y) -> {
            double k;
            if (dir == 0 || dir == 2) {
                k = (y - intersection.getTop()) * 1.0 / intersection.getHeight();
            } else {
                k = (x - intersection.getLeft()) * 1.0 / intersection.getWidth();
            }
            for (int i = 0; i < 3; i++) {
                double[] front = getResultRgbBySxy(x, y, frontArea, i);
                double[] behind = getResultRgbBySxy(x, y, behindArea, i);
                setSmoothResult(front, behind, k);
            }
        });
    }

    /**
     * 通过 K 系数平滑结果（线性平滑）
     * 如果需要修改平滑曲线，仅修改该处即可
     *
     * @param front  位于前方的系数
     * @param behind 位于后方的系数
     * @param k      值
     */
    private void setSmoothResult(double[] front, double[] behind, double k) {
        double r = (behind[0] - front[0]) * k + front[0];
        double g = (behind[1] - front[1]) * k + front[1];
        double b = (behind[2] - front[2]) * k + front[2];
        front[0] = behind[0] = r;
        front[1] = behind[1] = g;
        front[2] = behind[2] = b;
    }

    /**
     * 计算区域间的亮度比例系数（差异）
     *
     * @param a1       区域1
     * @param a2       区域2
     * @param dir      方向
     * @param colorIdx 颜色索引
     * @return 比例
     */
    private double computeCorrectK(Area a1, Area a2, int dir, int colorIdx) {
        Rect intersection = getIntersection(a1, a2, dir);
        double[] brightness = {0, 0};
        intersection.foreach((sx, sy) -> {
            double[] rgb1 = getResultRgbBySxy(sx, sy, a1, colorIdx);
            double[] rgb2 = getResultRgbBySxy(sx, sy, a2, colorIdx);
            if (rgb1 == null || rgb2 == null) return;
            brightness[0] += rgb1[0] + rgb1[1] + rgb1[2];
            brightness[1] += rgb2[0] + rgb2[1] + rgb2[2];
        });
        return brightness[0] / brightness[1];
    }

    /**
     * 区域交集区域
     *
     * @param a1  区域1
     * @param a2  区域2
     * @param dir 方向
     * @return 交集区域
     */
    private Rect getIntersection(Area a1, Area a2, int dir) {
        return computeIntersection(a1.getUseIntersectRect(dir), a2.getUseIntersectRect(dir));
    }

    /**
     * 根据绝对坐标，获取区域中的亮度系数
     *
     * @param sx       绝对坐标
     * @param sy       绝对坐标
     * @param area     区域
     * @param colorIdx 颜色索引
     * @return 亮度系数
     */
    private double[] getResultRgbBySxy(int sx, int sy, Area area, int colorIdx) {
        return area.getResult(colorIdx).get(xy2Rxy(sx, sy, area));
    }


    /**
     * 将绝对坐标转换为相对坐标
     *
     * @param x    x 坐标
     * @param y    y 坐标
     * @param area 区域
     * @return 相对坐标
     */
    private IntPoint xy2Rxy(int x, int y, Area area) {
        x -= area.getShowArea().getLeft();
        y -= area.getShowArea().getTop();
        return new IntPoint(x, y);
    }

    /**
     * 将绝对坐标转换为相对坐标
     *
     * @param point 绝对坐标
     * @param area  区域
     * @return 相对坐标
     */
    private IntPoint xy2Rxy(IntPoint point, Area area) {
        return xy2Rxy(point.getX(), point.getY(), area);
    }

    /**
     * 区域融合所需参数
     */
    public static class Params {
        public List<Area> areas;
    }

    /**
     * 区域对象，区域融合专用对象
     */
    @Getter
    @Setter
    public static class Area extends CorrectArea {

        // [R,G,B] 矫正结果
        private List<Matrix<double[]>> results;

        public static Area of(CorrectArea area) {
            Area a = new Area();
            a.setAreaId(area.getAreaId());
            a.setValidArea(area.getValidArea());
            a.setShowArea(area.getShowArea());
            return a;
        }

        /**
         * 获取结果
         *
         * @param colorIdx 颜色索引
         * @return 结果
         */
        public Matrix<double[]> getResult(int colorIdx) {
            return results.get(colorIdx);
        }
    }
}
